package com.dash.dashrecoder;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.*;
import java.util.concurrent.Executor;

import android.os.AsyncTask;
import android.util.Log;
import android.widget.ProgressBar;

import com.coremedia.iso.IsoFile;
import com.coremedia.iso.boxes.TimeToSampleBox;
import com.googlecode.mp4parser.authoring.Movie;
import com.googlecode.mp4parser.authoring.Track;
import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;

public enum Segmenter {
	
	INSTANCE; //Singleton
//	Executor executor;
	
	Segmenter() {
//		this.executor = new SerialExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
	}
	
	public void segmentVideo(String selectFileName,String filePath, double targetSegmentLength, UpdateProgressBar updateProgressBarCallBack/*ProgressBar segmentProgressBar*//*SegmenterCallback callback*/) {
		new SegmentVideoTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
				SegmentVideoTaskParam.create(selectFileName, filePath, targetSegmentLength, updateProgressBarCallBack/*segmentProgressBar*//*, callback*/));
	}
	
}

class SegmentVideoTaskParam {
	
	static SegmentVideoTaskParam create(String selectFileName, String filePath, double targetSegmentLength,UpdateProgressBar updateProgressBarCallBack /*ProgressBar segmentProgressBar*//*, SegmenterCallback callback*/) {
		SegmentVideoTaskParam param = new SegmentVideoTaskParam();
		param.selectFileName = selectFileName;
		param.filePath = filePath;
		param.targetSegmentLength = targetSegmentLength;
		param.updateProgressBarCallBack = updateProgressBarCallBack;
		/*param.segmentProgressBar = segmentProgressBar;*/
		/*param.callback = callback;*/
		return param;
	}
	
	//select filename which will be segmet
	String selectFileName;
	String filePath;
	double targetSegmentLength;
	UpdateProgressBar updateProgressBarCallBack;
	/*ProgressBar segmentProgressBar;*/
	/*SegmenterCallback callback;*/
	
}

class SegmentVideoTaskProgress {
	static SegmentVideoTaskProgress create(String segmentFilePath, boolean isFinalSegment, int percent) {
		SegmentVideoTaskProgress inst = new SegmentVideoTaskProgress();
		inst.segmentFilePath = segmentFilePath;
		inst.isFinalSegment = isFinalSegment;
		inst.percent = percent;
		return inst;
	}
	
	String segmentFilePath;
	boolean isFinalSegment;
	int percent;
}

class SegmentVideoTask extends AsyncTask <SegmentVideoTaskParam, SegmentVideoTaskProgress, Integer> {
	
    protected String 			selectFileName;
	protected String 			filePath;
	protected double 			targetSegmentLength;
/*	protected SegmenterCallback callback;*/
	
	private Movie				movie;
	private List<Track> 		tracks;
	private Track				videoTrack;
	private long				videoTrackDuration;
	
	private static final String TAG = "SegmentVideoTask";
	/*private ProgressBar segmentProgressBar = null;*/
	private UpdateProgressBar updateProgressBarCallBack;
	@Override
	protected Integer doInBackground(SegmentVideoTaskParam... param) {
		this.targetSegmentLength = param[0].targetSegmentLength;
		/*this.callback = param[0].callback;*/
		this.filePath = param[0].filePath;
/*		this.segmentProgressBar = param[0].segmentProgressBar;*/
		this.updateProgressBarCallBack = param[0].updateProgressBarCallBack;
		//selectFileName
		this.selectFileName = param[0].selectFileName;
		return segmentVideo();
	}
	
	protected void onProgressUpdate(SegmentVideoTaskProgress... progress) {
		/*if (this.callback != null) {
			callback.onSegmentCreated(progress[0].segmentFilePath, progress[0].isFinalSegment, progress[0].percent);
		}*/
		/*segmentProgressBar.setProgress(progress[0].percent);*/
		updateProgressBarCallBack.updateSegmentProgressBar(progress[0].percent);
	}
	
	protected Integer segmentVideo() {
		FileInputStream movieStream = null;
		long timerStart = System.currentTimeMillis();
		
		try {
			movieStream = new FileInputStream(this.filePath);
			this.movie = MovieCreator.build(movieStream.getChannel());
			this.tracks = this.movie.getTracks();
			this.videoTrack = findVideoTrack();
			if (this.videoTrack == null)
				/*return DashResult.INVALID_FORMAT;*/
				return -1;
			
			this.videoTrackDuration = getDuration(this.videoTrack);
			
			double startTime = correctTimeToSyncSample(this.videoTrack, 0.0, false);;
			double endTime   = correctTimeToSyncSample(this.videoTrack, startTime + this.targetSegmentLength, true);
			int segmentId = 0;
			
			while (startTime < endTime) {
				Log.d(TAG, "adjusted start=" + startTime + ", end=" + endTime);
				
				this.movie.setTracks(new LinkedList<Track>());
				//��ʼ�ü�
				cropNextSegmentWithAdjustedTime(startTime, endTime);
				
				/*String segmentFilePath = Storage.getFileForSegment(new File(this.filePath), segmentId).getPath();*/
				//segmetFile store path
				String segmentFilePath = "/mnt/sdcard/MovieSegments/"+selectFileName.substring(0,selectFileName.lastIndexOf("."))+ "_" + segmentId + ".mp4";
				Log.d(TAG, "Segment file path=" + segmentFilePath);
				writeMovieFile(segmentFilePath);
				
				int progress = (int) ((100 * endTime) / (this.videoTrackDuration / 100000));
				if (progress > 100) {
					progress = 100;
				}
				Log.d(TAG, "End time=" + endTime + " Track duration=" + this.videoTrackDuration + " Progress=" + progress);
				
				//Find next segment's start time and end time
				//If next segment's start is equal to its end (duration=0), then this is the final segment
				startTime = endTime;
				endTime   = correctTimeToSyncSample(this.videoTrack, startTime + this.targetSegmentLength, true);
				
		        publishProgress(SegmentVideoTaskProgress.create(segmentFilePath, (startTime == endTime), progress));
		        
				++segmentId;
			}
		} catch (FileNotFoundException e) {
			Log.e(TAG, "File not found: " + e.getMessage());
			/*return DashResult.FILE_NOT_FOUND;*/
			return -1;
		} catch (IOException e) {
			Log.e(TAG, "IO error: " + e.getMessage());
			/*return DashResult.IO_ERROR;*/
			return -1;
		} finally {
			try { movieStream.close(); } catch (Exception e) {}
		}
		
		long timerEnd = System.currentTimeMillis();
		Log.i(TAG, "Segmentation finished in " + (timerEnd - timerStart) + "ms");
		
		/*return DashResult.OK;*/
		return 0;
	}
	
	private void cropNextSegmentWithAdjustedTime(double adjustedStartTime, double adjustedEndTime) {
		this.movie.setTracks(new LinkedList<Track>());
		
		for (Track track : this.tracks) {
			long currentSample = 0;
			double currentTime = 0;
			long startSample = -1;
			long endSample = -1;
			
			for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
				for (int i = 0; i < entry.getCount(); i++) {
					if (currentTime <= adjustedStartTime) {
						// current sample is still before the new start time
						startSample = currentSample;
					}
					
					if (currentTime <= adjustedEndTime) {
						// current sample is after the new start time and still before the new end time
						endSample = currentSample;
					} else {
						// current sample is after the end of the cropped video
						break;
					}
					currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale();
                    currentSample++;
				}
			}
			this.movie.addTrack(new CroppedTrack(track, startSample, endSample));
		}
	}
	
	protected void writeMovieFile(String filePath) {
		try {
			IsoFile out = new DefaultMp4Builder().build(this.movie);
			FileOutputStream fos = new FileOutputStream(filePath);
			FileChannel fc = fos.getChannel();
	        out.getBox(fc);
	        fc.close();
	        fos.close();
		} catch (IOException e) {
			Log.d(TAG, "IO error: " + e.getMessage());
		}
	}
	
	protected Track findVideoTrack() {
		for (Track track : this.tracks) {
			if (isVideoTrack(track))
				return track;
		}
		return null;
	}
	
	protected static boolean isVideoTrack(Track track) {
		return (track.getMediaHeaderBox().getType().equals("vmhd"));
	}
	
	protected static long getDuration(Track track) {
        long duration = 0;
        for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
            duration += entry.getCount() * entry.getDelta();
        }
        return duration;
    }
	
    private static double correctTimeToSyncSample(Track track, double cutHere, boolean next) {
        double[] timeOfSyncSamples = new double[track.getSyncSamples().length];
        long currentSample = 0;
        double currentTime = 0;
        for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) {
            TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i);
            for (int j = 0; j < entry.getCount(); j++) {
                if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) {
                    // samples always start with 1 but we start with zero therefore +1
                    timeOfSyncSamples[Arrays.binarySearch(track.getSyncSamples(), currentSample + 1)] = currentTime;
                }
                currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale();
                currentSample++;
            }
        }
        double previous = 0;
        for (double timeOfSyncSample : timeOfSyncSamples) {
            if (timeOfSyncSample > cutHere) {
                if (next) {
                    return timeOfSyncSample;
                } else {
                    return previous;
                }
            }
            previous = timeOfSyncSample;
        }
        return timeOfSyncSamples[timeOfSyncSamples.length - 1];
    }	
}
